package org.tau.workshop2011.widgets

import scala.collection.mutable.Queue
import java.awt.{Dimension, Component}
import javax.swing.{ScrollPaneConstants, JScrollPane, JComponent}
import org.tau.workshop2011.codegen.{InitialProperty, UpdateManager, DSLProperty}

/**
 * A container for laying out several widgets in a row/column, where some of
 * them may be resizeable (if 2 or more widgets are resieable along that axis,
 * splitters will appear next to resizeable widgets to allow resizing).
 *
 * The implementation is based on several nested JSplitPane widgets, where the
 * widgets themselves are organized inside JPanel panels with the SpringLayout
 * layout manager
 *
 * @author Barak Itkin
 */
class MultiSplitPane(val orientation: Orientation, val mspParent: MultiSplitPane = null, val width: Option[DSLProperty[Int]],  val height: Option[DSLProperty[Int]]) extends NativeWidgetWrapper with HasOrientation {

  /* Import all the orientation utilities */
  import SizingUtils._

  /* Implement the HasOrientation trait */
  def getMainOrientation: Orientation = orientation

  /* Implicit conversion to wrap widgets with the current orientation */
  implicit def orientNativeWidget(nw: NativeWidgetWrapper): OrientedNativeWidgetWrapper = OrientedNativeWidgetWrapper(nw, orientation)

  protected var firstOneSplit: OneSplit = new OneSplit(orientation = orientation);
  protected var lastOneSplit: OneSplit = firstOneSplit;
  protected var lastWasResizeable: Boolean = false;

  protected var addingQueue: Queue[NativeWidgetWrapper] = new Queue

  def add(comp: NativeWidgetWrapper) {
    addingQueue enqueue comp
  }

  protected def realAdd(nWidget: NativeWidgetWrapper, moreResizeablesComing: Boolean) {
    val comp: JComponent = nWidget.getNativeWidget

    if (lastWasResizeable && moreResizeablesComing) {
      lastOneSplit = lastOneSplit.split();
    }
    lastOneSplit.add(nWidget);
    if (nWidget.isResizeable) {
      val compSizes = getMinMaxPrefSizes(comp)
      oSetSize(compSizes.min, 0)
      oSetSize(compSizes.max, MAX_DIM_VALUE)
      setMinMaxPrefSizes(comp, compSizes)
    }
    lastWasResizeable = nWidget.isResizeable;
  }

  def callAfterAddingLastChild() {
    val resizeableCount = (addingQueue filter {
      (widget) => widget.isResizeable
    }).length
    var i = resizeableCount

    while (!addingQueue.isEmpty) {
      val nWidget = addingQueue.dequeue()
      if (nWidget.isResizeable)
        i -= 1

      realAdd(nWidget, i == 0)
    }

    lastOneSplit.lock();
  }

//  def setSize(comp: NativeWidgetWrapper, d: Dimension) {
//    if (comp.isResizeable)
//      throw new IllegalStateException("The size of resizeable widgets should not be " +
//        "set explicitly");
//
//    var current: OneSplit = lastOneSplit
//    var previous: OneSplit = null;
//
//    /* We want to find the OneSplit that contains this component */
//    while (current != null && !current.children.contains(comp)) {
//      previous = current;
//      current = current.parent;
//    }
//
//    /* Now, if we haven't found any OneSplit containing this component, we
//     * should throw an error saying so */
//    if (current == null)
//      throw new IllegalArgumentException("The component " + comp + " was not found");
//
//    /**
//     * The logic is simple. If there is a resizeable component inside a OneSplit
//     * then it has to be the last component before the split (in case where
//     * there is a split). But, wait. Sometimes we won't be able '
//     */
//
//    val currentSize = orientation.getSize(comp.getNativeWidget.getPreferredSize)
//    val newSize = orientation.getSize(d)
//    val diff = currentSize - newSize
//
//    setMinMaxPrefSizes(comp.getNativeWidget, d, d, d)
//    comp.getNativeWidget.invalidate()
//
//    current.addFreeSpace(diff)
//
//    while (current != null && current.parent != null) {
//      current.changeWrapperSize(-diff)
//      current = current.parent
//    }
//
//    lastOneSplit.refreshSizes();
//
//    lastOneSplit.panel.invalidate()
//
//    // TODO: this entire block would be un-necessary if we can find how to tell
//    // the JSplitPane to reclaculate the divider location by it's own (like it
//    // does when we resize the window on this axis). For now, we do our best
//    // approximation
//    current = lastOneSplit
//    while (current != null) {
//      if (current.splitPane != null) {
//        val sp = current.splitPane
//        sp.setSize(new Dimension(sp.getPreferredSize))
//
//        val left = sp.getDividerLocation - sp.getDividerSize / 2
//        val right = oGetSize(sp.getSize) - left - sp.getDividerSize
//
//        val minLeft = oGetSize(sp.getLeftComponent.getMinimumSize)
//        val minRight = oGetSize(sp.getRightComponent.getMinimumSize)
//
//        val maxLeft = oGetSize(sp.getLeftComponent.getMaximumSize)
//        val maxRight = oGetSize(sp.getRightComponent.getMaximumSize)
//
//        val total = left + right
//
//        val diff = total - minLeft - minRight
//        val diffMax = total - maxLeft - maxRight
//
//        // TODO: increase the "smartness" of this block to handle maximal sizes
//        // and, preferred sizes, etc. Especially in the case where the trivial
//        // solution to one problem, would give rise to another.
//        // Note: This is EXTREMELY LOW PRIORITY
//
//        // No need to interfere unless something is too big or too small
//        var proposedLeft = left
//
//        if (minLeft > left || minRight > right) {
//          proposedLeft = minLeft + (sp.getResizeWeight * diff).asInstanceOf[Int]
//          sp.setDividerLocation(proposedLeft)
//        } else if (maxLeft < left) {
//          proposedLeft = maxLeft
//        } else if (maxRight < right) {
//          proposedLeft = total - maxRight
//        }
//      }
//      current = current.parent
//    }
//
//    root.revalidate()
//
//    debug
//  }

  def debug {
    for ((sizeFunc, name) <- List((getMinSize _, "min"), (getPrefSize _, "pref"), (getMaxSize _, "max"))) {
      print(name + ": ")
      debug(firstOneSplit, sizeFunc)
      println()
    }
  }

  def debug(current: OneSplit, sizeFunc: (Component) => Dimension) {

    if (current == null)
      return

    if (current.getRoot != current.panel) {
      print("( ")
    }

    print("( ")
    for (child <- current.children) {
      val dim = sizeFunc(child.getNativeWidget)
      print(dim.width + "x" + dim.height + " ")
    }
    print(") ")
    print(sizeFunc(current.panel).width + "x" + sizeFunc(current.panel).height)

    if (current.OSchild != null) {
      print(" | ")
      debug(current.OSchild, sizeFunc)
    }

    if (current.getRoot != current.panel) {
      print(") ")
      print(sizeFunc(current.splitPane).width + "x" + sizeFunc(current.splitPane).height)
    }

  }


  var scrollPaneWidth: Option[DSLProperty[Int]] = None
  var scrollPaneHeight: Option[DSLProperty[Int]] = None
  var scrollPane: JScrollPane = null

  def callAfterLastModification(um: UpdateManager) {
    lastOneSplit.refreshSizes(recursive = true)

    // The main size is well defined if:
    // 1. we have no resizeable widgets
    // 2. We have ONE resizeable widget and the outer size is well defined

    var allChildren: Set[NativeWidgetWrapper] = Set()
    var allMainDependencies: Set[DSLProperty[Int]] = Set()
    var allSecondDependencies: Set[DSLProperty[Int]] = Set()

    var iter = firstOneSplit
    while (iter != null) {
      allChildren ++= iter.children
      for (val child: NativeWidgetWrapper <- iter.children) {
        child.getMainSize match {
          case Some(size) => allMainDependencies += size
          case None =>
        }
        child.getSecondSize match {
          case Some(size) => allSecondDependencies += size
          case None =>
        }
      }

      iter = iter.OSchild
    }

    val mainInnerWellDefined = (allChildren filter {
      (widget) => widget.getMainSize == None
    }).isEmpty
    if (mainInnerWellDefined) assert (firstOneSplit == lastOneSplit)

    var secondInnerSizes: Set[Int] = Set()
    allChildren foreach { (widget) => if (widget.getSecondSize != None) secondInnerSizes += widget.getSecondSize.get.get }
    val secondInnerWellDefined = secondInnerSizes.size == 1

    scrollPane = new JScrollPane(firstOneSplit.getRoot)
    scrollPane.setBorder(null)

    val realWidth: Option[Int] =
/*      if (explicitSize && width == None && height == None)
        None
      else */width match {
        case Some(givenWidth) => Some(givenWidth.get)
        case None => if (orientation == Orientation.HORIZONTAL && mainInnerWellDefined) {
          var result = 0
          allChildren foreach { (child) => result += child.getWidth.get.get }
          Some(result)
        } else if (orientation == Orientation.VERTICAL && secondInnerWellDefined) {
          Some(secondInnerSizes.head)
        } else None
      }

    val realHeight: Option[Int] =
/*      if (explicitSize && height == None && width == None)
        None
      else */height match {
        case Some(givenHeight) => Some(givenHeight.get)
        case None => if (orientation == Orientation.VERTICAL && mainInnerWellDefined) {
          var result = 0
          allChildren foreach { (child) => result += child.getHeight.get.get }
          Some(result)
        } else if (orientation == Orientation.HORIZONTAL && secondInnerWellDefined) {
          Some(secondInnerSizes.head)
        } else None
      }

    realWidth match {
      case Some(size) => {
        mutateAllComponentSizes(scrollPane, {(dim:Dimension) => new Dimension(size, dim.height)})
        scrollPaneWidth = Some(new InitialProperty[Int](size, um))
      }
      case None =>
    }

    realHeight match {
      case Some(size) => {
        mutateAllComponentSizes(scrollPane, {(dim:Dimension) => new Dimension(dim.width, size)})
        scrollPaneHeight = Some(new InitialProperty[Int](size, um))
      }
      case None =>
    }

    val hScroll = (mainInnerWellDefined && orientation == Orientation.HORIZONTAL || secondInnerWellDefined && orientation == Orientation.VERTICAL) && width != None
    val vScroll = (mainInnerWellDefined && orientation == Orientation.VERTICAL || secondInnerWellDefined && orientation == Orientation.HORIZONTAL) && height != None

    // Now, since when having a scroller it takes space of the other dimension,
    // we would like to add a scroller also in that dimension
    if (hScroll || vScroll) {
      scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED)
      scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED)
    } else {
      scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED)
      scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED)
    }
  }

  override def getNativeWidget = scrollPane
  override def getWidth = scrollPaneWidth
  override def getHeight = scrollPaneHeight

}
